Skip to content

nixos/beszel: init#380731

Merged
h7x4 merged 3 commits intoNixOS:masterfrom
BonusPlay:module/beszel
Oct 22, 2025
Merged

nixos/beszel: init#380731
h7x4 merged 3 commits intoNixOS:masterfrom
BonusPlay:module/beszel

Conversation

@BonusPlay
Copy link
Member

Beszel is a lightweight server monitoring platform that includes Docker statistics, historical data, and alert functions. It normally is deployed using docker, but due to golang, it's quite easy to deploy as systemd service as well.

While I've been testing this module in my homelab, I've noticed #379932 popped up, but that creates only agent (and is marked as WIP), while this has both agent + hub.

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 25.05 Release Notes (or backporting 24.11 and 25.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog This PR adds or changes release notes 8.has: module (update) This PR changes an existing module in `nixos/` labels Feb 9, 2025
@BonusPlay BonusPlay changed the title Module/beszel module/beszel: init Feb 9, 2025
@github-actions github-actions bot added the 8.has: documentation This PR adds or changes documentation label Feb 9, 2025
@BonusPlay BonusPlay added 8.has: module (new) This PR adds a module in `nixos/` and removed 8.has: module (update) This PR changes an existing module in `nixos/` labels Feb 9, 2025
@github-actions github-actions bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. labels Feb 9, 2025
@arunoruto arunoruto mentioned this pull request Mar 6, 2025
13 tasks
@github-actions github-actions bot added the 8.has: module (update) This PR changes an existing module in `nixos/` label Mar 6, 2025
@BonusPlay BonusPlay changed the title module/beszel: init nixos/beszel: init Mar 7, 2025
@BonusPlay BonusPlay force-pushed the module/beszel branch 2 times, most recently from fcd3bba to beea285 Compare March 7, 2025 14:05
@wegank wegank added the 12.approvals: 1 This PR was reviewed and approved by one person. label Mar 9, 2025
Copy link
Contributor

@hatch01 hatch01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works in production for a week.

@nixpkgs-ci nixpkgs-ci bot added 12.approvals: 3+ This PR was reviewed and approved by three or more persons. and removed 12.approvals: 2 This PR was reviewed and approved by two persons. labels Oct 9, 2025
@BonusPlay
Copy link
Member Author

Been working on production for half a year now.

PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the pictures from the project readme, it looks like the service has a feature for monitoring disk usage. Could it be that the agent (not the hub) won't get access to anything under /home with it marked as private, and therefore reports disk usage incorrectly? I imagine it will still get the overall disk usage correct, just not for /home and other blocked off paths maybe?

@arunoruto
Copy link
Contributor

arunoruto commented Oct 10, 2025

@h7x4 I can't comment on the last review (not sure why?), so I will do it here:
This has been mentioned before, and I proposed to use tempfs instead of yes for the ProtectHome attribute. A possibility is also to make it read-only, but I am not sure how much it would differ from not enabling the option at all. The process could still read everything in the home directories, which is not needed by the service, it just needs access to it so it can read how much is of the drive is used.

https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#ProtectHome=

@h7x4
Copy link
Member

h7x4 commented Oct 10, 2025

and I proposed to use tempfs instead of yes for the ProtectHome attribute.

This would still lead to the service reporting 0 disk usage for the home directories, right? The tmpfs option just mounts a freshly created empty tmpfs over /home.

A possibility is also to make it read-only, but I am not sure how much it would differ from not enabling the option at all.

Upstream seems to suggest that this is the intended way to do it

@arunoruto
Copy link
Contributor

arunoruto commented Oct 12, 2025

I implemented the changes for the agent (which I can also test to a certain degree) to take a bit of the burden off of @BonusPlay.

@h7x4, regarding the ProtectHome variable: you commented on the hub, but I think it should be on the agent, right?
And btw, it is a bit unrelated, but could take a look at #449260. It should be kinda trivial, since it is just a version update with small tweaks to reflect the new changes in the project.

Final beszel-agent.nix
{
  lib,
  config,
  pkgs,
  ...
}:
let
  cfg = config.services.beszel.agent;
in
{
  meta.maintainers = with lib.maintainers; [
    BonusPlay
    arunoruto
  ];

  options.services.beszel.agent = {
    enable = lib.mkEnableOption "beszel agent";
    package = lib.mkPackageOption pkgs "beszel" { };
    openFirewall = (lib.mkEnableOption "") // {
      description = "Whether to open the firewall port (default 45876).";
    };

    environment = lib.mkOption {
      type = lib.types.attrsOf lib.types.str;
      default = { };
      description = ''
        Environment variables for configuring the beszel-agent service.
        This field will end up public in /nix/store, for secret values use `environmentFile`.
      '';
    };
    environmentFile = lib.mkOption {
      type = lib.types.nullOr lib.types.path;
      default = null;
      description = ''
        File path containing environment variables for configuring the beszel-agent service in the format of an EnvironmentFile. See {manpage}`systemd.exec(5)`.
      '';
    };
    extraPath = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = [ ];
      description = ''
        Extra packages to add to beszel path (such as nvidia-smi or rocm-smi).
      '';
    };
  };

  config = lib.mkIf cfg.enable {
    systemd.services.beszel-agent = {
      description = "Beszel Server Monitoring Agent";

      wantedBy = [ "multi-user.target" ];
      wants = [ "network-online.target" ];
      after = [ "network-online.target" ];

      environment = cfg.environment;
      path =
        cfg.extraPath
        ++ lib.optionals (builtins.elem "nvidia" config.services.xserver.videoDrivers) [
          (lib.getBin config.hardware.nvidia.package)
        ]
        ++ lib.optionals (builtins.elem "amdgpu" config.services.xserver.videoDrivers) [
          (lib.getBin pkgs.rocmPackages.rocm-smi)
        ]
        ++ lib.optionals (builtins.elem "intel" config.services.xserver.videoDrivers) [
          (lib.getBin pkgs.intel-gpu-tools)
        ];

      serviceConfig = {
        ExecStart = ''
          ${cfg.package}/bin/beszel-agent
        '';

        EnvironmentFile = cfg.environmentFile;

        # adds ability to monitor docker/podman containers
        SupplementaryGroups =
          lib.optionals config.virtualisation.docker.enable [ "docker" ]
          ++ lib.optionals (
            config.virtualisation.podman.enable && config.virtualisation.podman.dockerSocket.enable
          ) [ "podman" ];

        DynamicUser = true;
        User = "beszel-agent";
        LockPersonality = true;
        NoNewPrivileges = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProtectClock = true;
        ProtectControlGroups = "strict";
        ProtectHome = "read-only";
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectSystem = "strict";
        Restart = "on-failure";
        RestartSec = "30s";
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallErrorNumber = "EPERM";
        SystemCallFilter = [ "@system-service" ];
        Type = "simple";
        UMask = 27;
      };
    };

    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
      (
        if (builtins.hasAttr "PORT" cfg.environment) then
          (lib.strings.toInt cfg.environment.PORT)
        else
          45876
      )
    ];
  };
}

Diff:

19c19,21
<     openFirewall = lib.mkEnableOption "Open the firewall port (default 45876).";
---
>     openFirewall = (lib.mkEnableOption "") // {
>       description = "Whether to open the firewall port (default 45876).";
>     };
22c24
<       type = lib.types.attrs;
---
>       type = lib.types.attrsOf lib.types.str;
47c49
<       description = "Beszel Agent";
---
>       description = "Beszel Server Monitoring Agent";
80a83
>         User = "beszel-agent";
86,87c89,90
<         ProtectControlGroups = true;
<         ProtectHome = true;
---
>         ProtectControlGroups = "strict";
>         ProtectHome = "read-only";

I can do the same for the hub, but someone else would need to test it!

@h7x4
Copy link
Member

h7x4 commented Oct 12, 2025

regarding the ProtectHome variable: you commented on the hub, but I think it should be on the agent, right?

Correct, only the agent needs access AFAIU. The hub gets the data from one or more agents and is only responsible for handling that info - not collect any more data. So it shouldn't need access to the home directories.

could take a look at #449260

👍

};

config = lib.mkIf cfg.enable {
systemd.services.beszel-agent = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the setup scripts and docs really seems to suggest that the agent wants a KEY/KEY_FILE of some sort. Since you've been dogfooding this for a while, I assume you set this in the environment file maybe?

Do you think it's important enough to warrant a separate option? Or at least a piece of documentation or description telling downstream module users to set it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it needs to be set (and is pretty much the only important config). However, it is technically a secret value, so KEY should be passed via here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure what you mean by "here", are you referring to the environment file? In any case, I really think it would be nice to communicate to the module user in some way that this piece of config really needs to be set. Either via a separate unset option keyFile that just sets the environment variable for them and makes the module system force them to configure it, via some piece of a description where the user is likely to see the notice, or via a meta.doc file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, most of the config options are done through the environment variables. I just read-up on how to set it up and came with the following snipper:

environment = lib.mkOption {
  type = lib.types.submodule {
    freeformType = with lib.types; attrsOf anything;
    options = {
      EXTRA_FILESYSTEMS = lib.mkOption {
        type = lib.types.str;
        default = "";
        example = ''
          lib.strings.concatStringsSep "," [
            "sdb"
            "sdc1"
            "mmcblk0"
            "/mnt/network-share"
          ];
        '';
        description = ''
          Comma separated list of additional filesystems to monitor extra disks.
          See <https://beszel.dev/guide/additional-disks>.
        '';
      };
      KEY = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = ''
          Public SSH key(s) to use for authentication. Provided in the hub.
        '';
      };
      KEY_FILE = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        default = null;
        description = ''
          Public SSH key(s) from a file instead of an environment variable. Provided in the hub.
        '';
      };
      TOKEN = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = ''
          WebSocket registration token. Provided in the hub.
        '';
      };
      TOKEN_FILE = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        default = null;
        description = ''
          WebSocket token from a file instead of an environment variable. Provided in the hub.
        '';
      };
    };
  };
  default = { };
  description = ''
    Environment variables for configuring the beszel-agent service.
    This field will end up public in /nix/store, for secret values use `environmentFile`.
  '';
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even having TOKEN and KEY options is a anti-pattern. We shouldn't encourage users to store secrets using plaintext in nix-store. If they do want, the can do that (via environment option).
We could add separate KEY_FILE and TOKEN_FILE options. wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably no need to expose EXTRA_FILESYSTEMS unless we have a sane default for it or expect most users to want to set it.

Copy link
Member

@h7x4 h7x4 Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second note, any chance the key could be provisioned programmatically somehow? Or is it one of those things where you just have to use the GUI? It would be kinda nice to have a nixos test for all of this...

Edit: found https://github.com/henrygd/beszel/blob/1e32d136506f44f191164c857f730b675adb3812/internal/hub/hub.go#L271 and https://github.com/henrygd/beszel/blob/1e32d136506f44f191164c857f730b675adb3812/internal/hub/hub.go#L224-L227, I'll see if I can cook up something (unless anyone else wants to have a go at it)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an ssh pubkey that is visible from hub's web-ui.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably no need to expose EXTRA_FILESYSTEMS unless we have a sane default for it or expect most users to want to set it.

So just the two _FILE options

On a second note, any chance the key could be provisioned programmatically somehow? Or is it one of those things where you just have to use the GUI? It would be kinda nice to have a nixos test for all of this...

There is a way to do so via the CLI:

Usage: beszel-agent [command] [flags]

Commands:
  health    Check if the agent is running
  help      Display this help message
  update    Update to the latest version
  version   Display the version

Flags:
  -key string
        Public key(s) for SSH authentication
  -listen string
        Address or port to listen on

But I am not sure if it is feasible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be where you give the agent the key from the hub, but I was looking for where to get the key from the hub in the first place. Think I found it though :)

@BonusPlay BonusPlay force-pushed the module/beszel branch 2 times, most recently from 68cd4dd to 399ada7 Compare October 12, 2025 15:58
RuntimeDirectory = baseNameOf cfg.dataDir;
ReadWritePaths = cfg.dataDir;

DynamicUser = true;
Copy link
Member

@h7x4 h7x4 Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if it would make sense to have this actually have a real system user. When running beszel-hub superuser create ..., the command just assumes that it's going to run as it is in cwd, and creates a new database right there. With a real user, the sysadmin could at least do sudo -u beszel-hub beszel-hub --dir /var/lib/beszel-hub/beszel_data superuser create ...

The alternative would be to put some sort of wrapper script in the environment that runs the commands as the service. There is a fancy new feature in systemd v258 systemd-analyze unit-shell that we might be able to use in a script that would run the commands with the environment of the unit. Using this tool would probably be quite experimental (for better or for worse). Both nsenter and sudo -u could be alternatives for the script, but I don't think they would work while the service is shut down. nsenter inherently so, and sudo -u because the user doesn't exist.

(See also nextcloud)

occ = pkgs.writeShellApplication {
name = "nextcloud-occ";
text =
let
command = ''
${lib.getExe' pkgs.coreutils "env"} \
NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
${phpCli} \
occ "$@"
'';
in
''
cd ${webroot}
# NOTE: This is templated at eval time
requiresRuntimeSystemdCredentials=${lib.boolToString requiresRuntimeSystemdCredentials}
# NOTE: This wrapper is both used in the internal nextcloud service units
# and by users outside a service context for administration. As such,
# when there's an existing CREDENTIALS_DIRECTORY, we inherit it for use
# in the nix_read_secret() php function.
# When there's no CREDENTIALS_DIRECTORY we try to use systemd-run to
# load the credentials just as in a service unit.
# NOTE: If there are no credentials that are required at runtime then there's no need
# to load any credentials.
if [[ $requiresRuntimeSystemdCredentials == true && -z "''${CREDENTIALS_DIRECTORY:-}" ]]; then
exec ${lib.getExe' config.systemd.package "systemd-run"} \
${
lib.escapeShellArgs (
map (credential: "--property=LoadCredential=${credential}") runtimeSystemdCredentials
)
} \
--uid=nextcloud \
--same-dir \
--pty \
--wait \
--collect \
--service-type=exec \
--setenv OC_PASS \
--setenv NC_PASS \
--quiet \
${command}
elif [[ "$USER" != nextcloud ]]; then
if [[ -x /run/wrappers/bin/sudo ]]; then
exec /run/wrappers/bin/sudo \
--preserve-env=CREDENTIALS_DIRECTORY \
--preserve-env=OC_PASS \
--preserve-env=NC_PASS \
--user=nextcloud \
${command}
else
exec ${lib.getExe' pkgs.util-linux "runuser"} \
--whitelist-environment=CREDENTIALS_DIRECTORY \
--whitelist-environment=OC_PASS \
--whitelist-environment=NC_PASS \
--user=nextcloud \
${command}
fi
else
exec ${command}
fi
'';
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're at it, did you create your superuser through the cli tool, or is there some other way around?

@BonusPlay
Copy link
Member Author

BonusPlay commented Oct 20, 2025

I know we can polish this module to perfection and bikeshed for another half a year, but this PR has been in decently-working state for over 8 months. I know it could be better, but I'd like to try get this module merged into 25.11 (we've already missed 25.05). @h7x4 is that possible?

After we get it merged I'm sure both me and @arunoruto plan to maintain it.

@h7x4
Copy link
Member

h7x4 commented Oct 21, 2025

I know we can polish this module to perfection and bikeshed for another half a year, but this PR has been in decently-working state for over 8 months. I know it could be better, but I'd like to try get this module merged into 25.11 (we've already missed 25.05). @h7x4 is that possible?

Yes, I'd be happy to get it merged by 25.11. It seems like most technical and UX issues with potential for backwards incompatibilities has been ironed out.

It took a little while, but I finished a nixos test. Do you mind if I push it on top of the current patchset?

EDIT: I see you've disabled maintainers pushing commits, here's the patch:

From 0bfad254ceb474554c9e4dea6d6a01937010e3cf Mon Sep 17 00:00:00 2001
From: h7x4 <h7x4@nani.wtf>
Date: Mon, 13 Oct 2025 12:52:20 +0900
Subject: [PATCH] nixos/tests/beszel: init

---
 nixos/tests/all-tests.nix          |   1 +
 nixos/tests/beszel.nix             | 119 +++++++++++++++++++++++++++++
 pkgs/by-name/be/beszel/package.nix |   6 +-
 3 files changed, 125 insertions(+), 1 deletion(-)
 create mode 100644 nixos/tests/beszel.nix

diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e471baaf7849..c879ec56938b 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -260,6 +260,7 @@ in
   beanstalkd = runTest ./beanstalkd.nix;
   bees = runTest ./bees.nix;
   benchexec = runTest ./benchexec.nix;
+  beszel = runTest ./beszel.nix;
   binary-cache = runTest {
     imports = [ ./binary-cache.nix ];
     _module.args.compression = "zstd";
diff --git a/nixos/tests/beszel.nix b/nixos/tests/beszel.nix
new file mode 100644
index 000000000000..77a4a32a3747
--- /dev/null
+++ b/nixos/tests/beszel.nix
@@ -0,0 +1,119 @@
+{ pkgs, lib, ... }:
+{
+  name = "beszel";
+  meta.maintainers = with lib.maintainers; [ h7x4 ];
+
+  nodes = {
+    hubHost =
+      { config, pkgs, ... }:
+      {
+        virtualisation.vlans = [ 1 ];
+
+        systemd.network.networks."01-eth1" = {
+          name = "eth1";
+          networkConfig.Address = "10.0.0.1/24";
+        };
+
+        networking = {
+          useNetworkd = true;
+          useDHCP = false;
+        };
+
+        services.beszel.hub = {
+          enable = true;
+          host = "10.0.0.1";
+        };
+
+        networking.firewall.allowedTCPPorts = [
+          config.services.beszel.hub.port
+        ];
+
+        environment.systemPackages = [
+          config.services.beszel.hub.package
+        ];
+      };
+
+    agentHost =
+      { config, pkgs, ... }:
+      {
+        virtualisation.vlans = [ 1 ];
+
+        systemd.network.networks."01-eth1" = {
+          name = "eth1";
+          networkConfig.Address = "10.0.0.2/24";
+        };
+
+        networking = {
+          useNetworkd = true;
+          useDHCP = false;
+        };
+
+        environment.systemPackages = with pkgs; [ jq ];
+
+        specialisation."agent".configuration = {
+          services.beszel.agent = {
+            enable = true;
+            environment.HUB_URL = "http://10.0.0.1:8090";
+            environment.KEY_FILE = "/var/lib/beszel-agent/id_ed25519.pub";
+            environment.TOKEN_FILE = "/var/lib/beszel-agent/token";
+            openFirewall = true;
+          };
+        };
+      };
+  };
+
+  testScript =
+    { nodes, ... }:
+    let
+      hubCfg = nodes.hubHost.services.beszel.hub;
+      agentCfg = nodes.agentHost.specialisation."agent".configuration.services.beszel.agent;
+    in
+    ''
+      import json
+
+      start_all()
+
+      with subtest("Start hub"):
+        hubHost.wait_for_unit("beszel-hub.service")
+        hubHost.wait_for_open_port(${toString hubCfg.port}, "${toString hubCfg.host}")
+
+      with subtest("Register user"):
+        agentHost.succeed('curl -f --json \'${
+          builtins.toJSON {
+            email = "admin@example.com";
+            password = "password";
+          }
+        }\' "${agentCfg.environment.HUB_URL}/api/beszel/create-user"')
+        user = json.loads(agentHost.succeed('curl -f --json \'${
+          builtins.toJSON {
+            identity = "admin@example.com";
+            password = "password";
+          }
+        }\' ${agentCfg.environment.HUB_URL}/api/collections/users/auth-with-password').strip())
+
+      with subtest("Install agent credentials"):
+        agentHost.succeed("mkdir -p \"$(dirname '${agentCfg.environment.KEY_FILE}')\" \"$(dirname '${agentCfg.environment.TOKEN_FILE}')\"")
+        sshkey = agentHost.succeed(f"curl -H 'Authorization: {user["token"]}' -f ${agentCfg.environment.HUB_URL}/api/beszel/getkey | jq -r .key").strip()
+        utoken = agentHost.succeed(f"curl -H 'Authorization: {user["token"]}' -f ${agentCfg.environment.HUB_URL}/api/beszel/universal-token | jq -r .token").strip()
+        agentHost.succeed(f"echo '{sshkey}' > '${agentCfg.environment.KEY_FILE}'")
+        agentHost.succeed(f"echo '{utoken}' > '${agentCfg.environment.TOKEN_FILE}'")
+
+      with subtest("Register agent in hub"):
+        agentHost.succeed(f'curl -H \'Authorization: {user["token"]}\' -f --json \'{${
+          builtins.toJSON {
+            "host" = "10.0.0.2";
+            "name" = "agent";
+            "pkey" = "{sshkey}";
+            "port" = "45876";
+            "tkn" = "{utoken}";
+            "users" = ''{user['record']['id']}'';
+          }
+        }}\' "${agentCfg.environment.HUB_URL}/api/collections/systems/records"')
+
+      with subtest("Start agent"):
+        agentHost.succeed("/run/current-system/specialisation/agent/bin/switch-to-configuration switch")
+        agentHost.wait_for_unit("beszel-agent.service")
+        agentHost.wait_until_succeeds("journalctl -eu beszel-agent --grep 'SSH connection established'")
+        agentHost.wait_until_succeeds(f'curl -H \'Authorization: {user["token"]}\' -f ${agentCfg.environment.HUB_URL}/api/collections/systems/records | grep agentHost')
+    '';
+}
diff --git a/pkgs/by-name/be/beszel/package.nix b/pkgs/by-name/be/beszel/package.nix
index 628592190f4d..39e50c6da52f 100644
--- a/pkgs/by-name/be/beszel/package.nix
+++ b/pkgs/by-name/be/beszel/package.nix
@@ -4,6 +4,7 @@
   fetchFromGitHub,
   nix-update-script,
   buildNpmPackage,
+  nixosTests,
 }:
 buildGoModule rec {
   pname = "beszel";
@@ -64,7 +65,10 @@ buildGoModule rec {
     mv $out/bin/hub $out/bin/beszel-hub
   '';
 
-  passthru.updateScript = nix-update-script { };
+  passthru = {
+    updateScript = nix-update-script { };
+    tests.nixos = nixosTests.beszel;
+  };
 
   meta = {
     homepage = "https://github.com/henrygd/beszel";
-- 
2.50.1

BonusPlay and others added 3 commits October 21, 2025 22:42
Co-authored-by: Mirza Arnaut <mirza.arnaut45@gmail.com>
Co-authored-by: Mirza Arnaut <mirza.arnaut45@gmail.com>
@nixpkgs-ci nixpkgs-ci bot added the 2.status: merge conflict This PR has merge conflicts with the target branch label Oct 21, 2025
@BonusPlay
Copy link
Member Author

I added @arunoruto as co-author to the commits since he proposed a lot of changes. I've also rebased on master and fixed a merge conflict. @h7x4 please check if beszel package has correct passthru section.

@nixpkgs-ci nixpkgs-ci bot removed the 2.status: merge conflict This PR has merge conflicts with the target branch label Oct 21, 2025
@nix-owners nix-owners bot requested a review from Bot-wxt1221 October 21, 2025 20:51
@h7x4 h7x4 added this pull request to the merge queue Oct 22, 2025
Merged via the queue into NixOS:master with commit fa77ae8 Oct 22, 2025
27 of 31 checks passed
@BonusPlay BonusPlay deleted the module/beszel branch October 22, 2025 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog This PR adds or changes release notes 8.has: documentation This PR adds or changes documentation 8.has: module (new) This PR adds a module in `nixos/` 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 12.approvals: 3+ This PR was reviewed and approved by three or more persons.

Projects

None yet

Development

Successfully merging this pull request may close these issues.